package com.xiaoyu.cloud.config;



import com.xiaoyu.cloud.handler.HttpClientResponseHandler;
import com.xiaoyu.cloud.properties.PoolProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;

/**
 * @author zgb
 * @desc ...
 * @date 2022-05-24 18:40:32
 */
@Slf4j
@Configuration
public class HttpClientConfig {
    @Autowired
    private PoolProperties poolProperties;


    @Bean
    public HttpClientResponseHandler httpClientResponseHandler() throws Exception {
        return new HttpClientResponseHandler();
    }

    @Bean
    public HttpClient httpClient() throws Exception {

        HttpClientBuilder b = HttpClientBuilder.create();

        // 设置信任所有证书
        SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
            @Override
            public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                return true;
            }
        }).build();
        b.setSSLContext(sslContext);

        HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;

        SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", sslSocketFactory)
                .build();


        //配置连接池
        PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(socketFactoryRegistry);

        //设置整个连接池最大连接数
        connMgr.setMaxTotal(poolProperties.getConnectMaxTimes());
        //MaxPerRoute路由是对maxTotal的细分,每个主机的并发，这里route指的是域名
        connMgr.setDefaultMaxPerRoute(poolProperties.getConnectMaxTimes() / 10);
        connMgr.setValidateAfterInactivity(5 * 1000); //在从连接池获取连接时，连接不活跃多长时间后需要进行一次验证
        b.setConnectionManager(connMgr);


        ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
            @Override
            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                HeaderElementIterator it = new BasicHeaderElementIterator
                        (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                while (it.hasNext()) {
                    HeaderElement he = it.nextElement();
                    String param = he.getName();
                    String value = he.getValue();
                    if (value != null && param.equalsIgnoreCase
                            ("timeout")) {
                        return Long.parseLong(value) * 1000;
                    }
                }
                return 60 * 1000;//如果没有约定，则默认定义时长为60s
            }
        };
        b.setKeepAliveStrategy(myStrategy);
        // 请求参数配置管理器
        RequestConfig requestConfig = RequestConfig.custom()
                //链接建立的超时时间
                .setConnectTimeout(poolProperties.getConnectTimeout())
                //响应超时时间
                .setSocketTimeout(poolProperties.getHandleTimeout())
                //http clilent中从connetcion pool中获得一个connection的超时时间
                .setConnectionRequestTimeout(poolProperties.getConnectTimeout())
                .setStaleConnectionCheckEnabled(true)
                .build();
        b.setDefaultRequestConfig(requestConfig);
        CloseableHttpClient httpClient = b.build();
        //对于实现了AutoCloseable接口的资源，可以使用try-with-resources来释放资源，就是在try中带资源的声明
        return httpClient;


    }

    //注意：使用setStaleConnectionCheckEnabled方法来逐出已被关闭的链接不被推荐。更好的方式是手动启用一个线程，定时运行closeExpiredConnections 和closeIdleConnections方法，如下所示。

    public static class IdleConnectionMonitorThread extends Thread {

        private final HttpClientConnectionManager connMgr;
        private volatile boolean shutdown;

        public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
            super();
            this.connMgr = connMgr;
        }

        @Override
        public void run() {
            try {
                while (!shutdown) {
                    synchronized (this) {
                        wait(5000);
                        // Close expired connections
                        connMgr.closeExpiredConnections();
                        // Optionally, close connections
                        // that have been idle longer than 30 sec
                        connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                    }
                }
            } catch (InterruptedException ex) {
                // terminate
            }
        }

        public void shutdown() {
            shutdown = true;
            synchronized (this) {
                notifyAll();
            }
        }

    }


}
